using System;
using System.IO;
using System.Net;
using System.Web;

namespace tvgrabau
{
	/// <summary>
	/// Provides classes to help setup YourTV cookie sessions.
	/// </summary>
	static public class Setup
	{
		/// <summary>
		/// Sets the region information cookie for YourTV, so that fetching will be region-specific.
		/// Required for retrieval of listings. Can be called again after main CookieContainer is cleared, to refresh programme fetch limit.
		/// </summary>
		/// <param name="FreeToAir">The free to air area code.</param>
		/// <param name="Cable">The cable provider code.</param>
		/// <param name="VenueList">String array of YourTV venue (channel) codes.</param>
		static public void SetRegion(string FreeToAir, string Cable, string[] VenueList)
		{	
			WebManager.GetPage("http://www.yourtv.com.au/profile/index.cfm");
			WebManager.GetPage("http://www.yourtv.com.au/profile/index.cfm?cookies=1&");
		
			string VenueData = "";
			foreach(string str in VenueList)
        	{
        		VenueData += "&venue_id="+str;
        	}
        	
            string regionPOSTdata = "fta_region_id=" + FreeToAir + "&pay_region_id=";
            
            if (Cable != "")
            {
                regionPOSTdata += Cable;
            }
            else
            {
                regionPOSTdata += "0";
            }
            
            regionPOSTdata += VenueData;
            
            string ReturnCheck = WebManager.GetPage("http://www.yourtv.com.au/profile/index.cfm?action=saveRegions", regionPOSTdata);
        	if (ReturnCheck.Length < 300)
        	{
        		if (MainClass.loud)
        		{
        			Console.WriteLine();
        			Console.WriteLine("Error while setting up region settings and unique ID - the data returned from the server (if any) was inappropriate.");
        			Console.WriteLine("Either YourTV has changed its region setup pages, their server is down, or something bad happened on your end.");
        			Console.WriteLine("Continuing anyway..");
        			Console.WriteLine();
        		}
        	}
        	
		}
	}
	
	
	static public class WebActions
	{
		public static int MirrorIndex = -1;
		public static string[] Mirrors;
		
		/// <summary>
		/// Picks and checks a random mirror from the Mirrors list.
		/// </summary>
		public static int PickRandomMirror()
		{
			Random rand = new Random((int)DateTime.Now.Ticks);
			bool works = false;
			int testmidx = -1; 
			int FallbackCounter = 5;	// 5 loops before fallback
			
			while (!works)
			{
				testmidx = rand.Next(WebActions.Mirrors.Length);
				string test = "";
				try
				{
					test = WebManager.GetPage(WebActions.Mirrors[testmidx] + "foo", false);
				}
				catch (Exception)
				{
					// Fail silently!
				}
				
				if (test.StartsWith("bar"))
				{
					works = true;
					MirrorIndex = testmidx;
				 	tvgrabau.OutputManager.BaseIconLocation = WebActions.Mirrors[testmidx] + "icons/";
				 	if (MainClass.loud)
				 	{
						Console.WriteLine("Using {0} as mirror...", WebActions.Mirrors[testmidx]);
					}
				}
				else if (FallbackCounter == 0)
				{
				    if (MainClass.loud)
				    {
					    Console.WriteLine("[WARNING] No active mirrors could be contacted. Falling back to standalone mode.");
					}
					works = true;
				}
				else if (test != "")
				{
				    if (MainClass.loud)
				    {
					    Console.WriteLine("{0} returned invalid data: {1}", WebActions.Mirrors[testmidx] + "foo", test);
					}
				}
				
				FallbackCounter--;
			}
			
			return testmidx;
		}
		
		/// <summary>
		/// Checks to see if the currently running instance of tvgrabau-yourtv is out of date.
		/// </summary>
		public static void CheckUpdates()
		{
			if (MirrorIndex > -1)
			{
				string rval = WebManager.GetPage(Mirrors[MirrorIndex] + "version", false).Replace("\n", "").Replace("\r", "");
				int remoteVersion = int.Parse(rval);
				System.Reflection.AssemblyName rv = System.Reflection.Assembly.GetExecutingAssembly().GetName();
				string lval = rv.Version.Build.ToString();
				int localVersion = int.Parse(lval);
				
				if (remoteVersion > localVersion)
				{
				    if (MainClass.loud)
				    {
					    Console.WriteLine();
					    Console.WriteLine("==[ Update Available! ]========================");
					    Console.WriteLine("The YourTV plugin requires an update! Please visit\n"+Mirrors[MirrorIndex] + "download to get a newer version.\nRunning: {0}. Latest: {1}.", lval, rval);
					}
					Environment.Exit(2);
				}
			}
		}
		
		private static string ConvertType(tvgrabau.ProgrammeType Type)
		{
			// TYPES:
			//	0 - Normal
			//	1 - Movie
			//	2 - Sports
			//	3 - Series
		
			if (Type == ProgrammeType.Movie)
			{
				return "1";
			}
			else if (Type == ProgrammeType.Series)
			{
				return "3";
			}
			else if (Type == ProgrammeType.Sports)
			{
				return "2";
			}
			else
			{
				return "0";
			}
		}
		
		/// <summary>
		/// Retrieves the listings table for the rest of the day.
		/// </summary>
		static public string GetRestDay()
		{
		    string PostData = "action=sessionTimes&region_id=" + MainClass.options.Region.FreeToAir;
			if (MainClass.options.Region.Cable != null && MainClass.options.Region.Cable != "0")
			{
				PostData += "%2C" + MainClass.options.Region.Cable;
			}
			
			PostData += "&date=" + DateTime.Now.ToString("yyyy-MM-dd");
			return WebManager.GetPage("http://www.yourtv.com.au/guide/index.cfm?action=restofday", PostData);
		}
		
		/// <summary>
		/// Fetches the listings table for the specified date and time.
		/// </summary>
		static public string GetPeriod(int Period, string Date)
		{
			string PostData = "action=sessionTimes&region_id=" + MainClass.options.Region.FreeToAir;
			if (MainClass.options.Region.Cable != null && MainClass.options.Region.Cable != "0")
			{
				PostData += "%2C" + MainClass.options.Region.Cable;
			}
			
			PostData += "&date=" + Date + "&period=" + Period.ToString();
			string x = WebManager.GetPage("http://www.yourtv.com.au/guide/index.cfm", PostData);
			return x;
		}
		
		/// <summary>
		/// This method retrieves raw programme information from either YourTV, or public mirrors.
		/// </summary>
		/// <param name="PID">The programme ID of which programme to fetch</param>
		/// <param name="YTV">If true, downloads raw programme data from YourTV, otherwise, Mediati Group mirrors.
		static public string GetProgramme(string PID, bool YTV)
		{
			if (YTV)
			{
				//string r = WebManager.GetPage("http://au.tv.yahoo.com/tv-guide/broker.html?event_id="+PID);
				string r = WebManager.GetPage(String.Format("http://www.yourtv.com.au/guide/index.cfm?action=session_info&event_id={0}&region_id={1}&sid={2}", PID, MainClass.options.Region.FreeToAir, YourTV.CFID));
				
				// Conform to delaySecs
				if (MainClass.options.FetchDelay > 0 && !MainClass.ar.FastMode)
				{
				    System.Threading.Thread.Sleep(MainClass.options.FetchDelay * 1000);
				}
				
				return r;
			}
			else
			{
				return WebManager.GetPage(Mirrors[MirrorIndex] + "api/get/?pid=" + PID, false);
			}
		}
		
		/// <summary>
		/// Uploads a programme to the currently selected public server.
		/// </summary>
		static public bool InsertProgramme(string PID, tvgrabau.Programme P)
		{
			string OldLabel = YourTV.Label;
			YourTV.Label = "Uploading programme...";
			YourTV.DrawProgress();
			string PostData = "";
			if (P.Title != null)
			{
				PostData += "t="+HttpUtility.UrlEncode(P.Title);
			}
			if (P.EpisodeName != null)
			{
				PostData += "&e="+HttpUtility.UrlEncode(P.EpisodeName);
			}
			if (P.Description != null)
			{
				PostData += "&d="+HttpUtility.UrlEncode(P.Description);
			}
			
			PostData += "&ts="+HttpUtility.UrlEncode(P.TimeStart.ToUniversalTime().ToString())+"&te="+HttpUtility.UrlEncode(P.TimeEnd.ToUniversalTime().ToString());
			
			if (P.Category != null)
			{
				PostData += "&c="+HttpUtility.UrlEncode(P.Category);
			}
			if (P.Country != null)
			{
				PostData += "&co="+HttpUtility.UrlEncode(P.Country);
			}
			
			PostData += "&w="+P.Widescreen.ToString()+"&r="+P.Repeat.ToString()+"&p="+P.Premiere.ToString()+"&tl="+P.Teletext.ToString();
			
			if (P.Classification != null)
			{
				PostData += "&ra="+HttpUtility.UrlEncode(P.Classification);
			}
			
			PostData += "&h="+P.HDTV.ToString()+"&ch="+HttpUtility.UrlEncode(P.ChannelName)+"&ty="+ConvertType(P.Type)+"&f="+P.Final.ToString();
			
			if (P.Year != null)
			{
				PostData += "&y="+P.Year;
			}
			if (P.Cast != null)
			{
				PostData += "&cas="+HttpUtility.UrlEncode(P.Cast);
			}
			if (P.Score != null)
			{
				PostData += "&scor="+P.Score.ToString();
			}
			
			string RemotePage = WebManager.GetPage(Mirrors[MirrorIndex] + "api/put/?pid=" + PID, PostData, false);
			YourTV.Label = OldLabel;
			YourTV.DrawProgress();
			if (RemotePage.IndexOf("<error>") > -1)
			{
				Console.WriteLine("[WARNING]: Got error from mirror during upload: {0}", RemotePage);
				return false;
			}
			else
			{
				return true;
			}
		}
	}
	
	
	static public class WebManager
	{
	    /// <summary>
	    /// Creates a random, imaginary IP address.
	    /// </summary>
	    public static string MakeFakeIP()
	    {
	        Random rand = new Random((int)System.DateTime.Now.Ticks);   // Create new Random with ticks as seed.
	        int two = rand.Next(0, 255);
	        int three = rand.Next(0, 255);
	        int four = rand.Next(0, 254) + 1;
	        
	        return String.Format("203.{0}.{1}.{2}", two, three, four);
	    }
	        
	    /// <summary>
	    /// Chooses a user-agent from a list at random.
	    /// </summary>
	    public static string MakeFakeUserAgent()
	    {
	        // Shouldn't we have this as a const/readonly?
	        string[] UserAgents = {
	            "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.5",
	            "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
	            "Mozilla/5.0 (Windows; U; Windows NT 6.0; nl) AppleWebKit/522.13.1 (KHTML, like Gecko) Version/3.0.2 Safari/522.13.1",
	            "Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en; rv:1.8.1.4pre) Gecko/20070511 Camino/1.6pre",
	            "Opera/9.22 (Windows NT 6.0; U; en)"
    	    };
    	    
	        Random rand = new Random((int)System.DateTime.Now.Ticks);
	        int item = rand.Next(0, UserAgents.Length);
	        
	        return UserAgents[item];
	    }
	    
	    /// <summary>
	    /// Fetches the webpage based on the URI given, in anonymous mode.
	    /// </summary>
		static public string GetPage(string uri)
		{
			return GetPage(uri, null, true);
		}
		
		/// <summary>
		/// Fetches the webpage based on the URI given.
		/// </summary>
		public static string GetPage(string uri, bool anon)
		{
		    return GetPage(uri, null, anon);
		}
		
		/// <summary>
		/// Fetches the webpage based on the URI given; sending the POST data with that request, in anonymous mode.
		/// </summary>
		public static string GetPage(string uri, string post)
		{
		    return GetPage(uri, post, true);
		}
				
        /// <summary>
        /// Fetches the webpage based on the URI given; sending the POST data with that request.
        /// </summary>
		static public string GetPage(string uri, string post, bool anon)
		{
		    // Don't even *try* to fetch data for empty URIs.
		    if (uri == "")
		    {
		        return "";
		    }
		    
			string s = uri;
			try
			{
				HttpWebRequest request = (HttpWebRequest)WebRequest.Create(s);
				
				// User has setup proxy for non-direct access to the internet.
				if (MainClass.options.Proxy.Enabled && !MainClass.options.Proxy.Tor)
				{
				    string proxyString = MainClass.options.Proxy.Hostname + ":" + MainClass.options.Proxy.Port;
				    WebProxy proxy = new WebProxy(String.Format("http://{0}/", proxyString), true);
				    
				    if (MainClass.options.Proxy.Username != "")
				    {
				        proxy.Credentials = new NetworkCredential(MainClass.options.Proxy.Username, MainClass.options.Proxy.Password);
				    }
				    
	    		    request.Proxy = proxy;
	    		}
	    			
				if (anon)
				{				    
				    string ip = MakeFakeIP();
				    request.Headers.Add("Via", "1.0 proxy:81 (Squid/2.3.STABLE3)");
				    request.Headers.Add("X-Forwarded-For", ip);
				    request.UserAgent = MakeFakeUserAgent();
				    
				    // If we're using Tor, only do it on 'anon' requests.
				    if (MainClass.options.Proxy.Enabled && MainClass.options.Proxy.Tor)
				    {
				        string proxyString = MainClass.options.Proxy.Hostname + ":" + MainClass.options.Proxy.Port;
				        //Console.WriteLine("Proxy string: {0}", proxyString);
    				    WebProxy proxy = new WebProxy(proxyString, true);
    	    		    request.Proxy = proxy;
				    }
				}			
				else
				{
				    // Have a sensible user-agent.
				    request.UserAgent = "tvgrabau/1.0";
				}
				
				if (post != null && post.Length > 0)
				{
					request.ContentType = "application/x-www-form-urlencoded";
					request.Method = "POST";
	        	    byte[] lbPostBuffer = System.Text.Encoding.GetEncoding(1252).GetBytes(post);
    	            request.ContentLength = lbPostBuffer.Length;

    		        Stream loPostData = request.GetRequestStream();
        		    loPostData.Write(lbPostBuffer, 0, lbPostBuffer.Length);
            		loPostData.Close();
				}
				
            	request.CookieContainer = YourTV.CookieContainer;
            	
				HttpWebResponse response = (HttpWebResponse)request.GetResponse();
				response.Cookies = request.CookieContainer.GetCookies(request.RequestUri);
            	Stream dataStream = response.GetResponseStream();
            	StreamReader reader = new StreamReader(dataStream);
	            
	            foreach (Cookie cook in response.Cookies)
	            {
	                if (cook.Name == "CFID")
	                {
	                    // This is our ID - store, and use later.
	                    YourTV.CFID = cook.Value;
	                }
	            }
	            
            	string responseFromServer = reader.ReadToEnd();
            	reader.Close();
            	dataStream.Close();
            	response.Close();
		
            	return responseFromServer;
            }
            catch (System.Net.WebException ex)
            {
                if (MainClass.loud)
                {
            	    Console.WriteLine();
            	    Console.WriteLine("[WARNING]: Non-fatal web exception occured while trying to get {0}: {1}", uri, ex.Message);
            	}
            	return "";
            }
            catch (System.Exception ex)
            {
                if (MainClass.loud)
                {
            	    Console.WriteLine();
            	    Console.WriteLine("[WARNING]: While trying to get \"{0}\", got: {1}.", uri, ex.Message);
            	}
            	return "";
            }
		}
	}
}
